/***************************************************************************
* @ProjectName	BrightCtrl												   *
* @Description	BrightCtrl lets you control the brightness (gamma really)  *
*				and the background light level of your LCD screen.		   *
* @Author		David Gonzalez Garcia.									   *
* @Web			davidxl.es												   *
* @license		No license, Open source									   *
***************************************************************************/

/** TODO **/
/*
	Disable Hotkeys to configure.
	Load & Save | File & registr
*/

#include <windows.h>
#include <Commctrl.h>
#include "debug.h"
#include <math.h>
#include "ids.h"

// Save & Load structs
struct HOTKEY
{
	WORD	enable;
    BYTE	mod;
    BYTE	key;
};

struct APP_SETTINGS
{
	HOTKEY	hotkeys[HOTKEY_COUNT];
	BYTE	gamma;
    BYTE	enable;
    BYTE	startWithWindows;
};

// main vars
const char		szAppName[] = "BrightCtrl";
HINSTANCE		hInst;
HMENU			tray_menu;
HICON 			tray_icon = NULL;
NOTIFYICONDATA	tray_data = {0};
APP_SETTINGS	appSettings = (APP_SETTINGS) {{
									{TRUE, MOD_ALT | MOD_CONTROL, '8'},
									{FALSE, 0 , 0},
									{TRUE, MOD_CONTROL, '7'},
									{TRUE, MOD_ALT, '0'},
									{TRUE, MOD_ALT, '9'},
									{TRUE, MOD_ALT, '8'},
									{FALSE, 0, 0},
									{FALSE, 0, 0}
								}, 9, TRUE, FALSE};

char strActions[HOTKEY_COUNT][22] = {
	"Close BrightCtrl",
	"Toggle Hotkeys",
	"PowerOff Screen",
	"Increment brightness",
	"Decrement brightness",
	"Default brightness",
	"Increment backlight",
	"Decrement backlight"
};

// Backlight
typedef struct _DISPLAY_BRIGHTNESS
{
    UCHAR ucDisplayPolicy;
    UCHAR ucACBrightness;
    UCHAR ucDCBrightness;
} DISPLAY_BRIGHTNESS, *PDISPLAY_BRIGHTNESS;

#define FILE_DEVICE_VIDEO   0x00000023
#define METHOD_BUFFERED     0
#define FILE_ANY_ACCESS     0

#define CTL_CODE(a,b,c,d) ((a)<<16 | (b)<<2 | (c)<<14 | (d))

#define IOCTL_VIDEO_QUERY_SUPPORTED_BRIGHTNESS \
    CTL_CODE(FILE_DEVICE_VIDEO, 0x125, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_VIDEO_QUERY_DISPLAY_BRIGHTNESS \
    CTL_CODE(FILE_DEVICE_VIDEO, 0x126, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS \
    CTL_CODE(FILE_DEVICE_VIDEO, 0x127, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define VideoQuerySupportedBrightness(a,b,c) \
    DeviceIoControl((HANDLE) a, IOCTL_VIDEO_QUERY_SUPPORTED_BRIGHTNESS,NULL, \
			0, (LPVOID) b, sizeof(DISPLAY_BRIGHTNESS),(LPDWORD) c, NULL);

#define VideoQueryDisplayBrightness(a,b,c) \
    DeviceIoControl((HANDLE) a, IOCTL_VIDEO_QUERY_DISPLAY_BRIGHTNESS, NULL, \
			0, (LPVOID) b, sizeof(DISPLAY_BRIGHTNESS),(LPDWORD) c, NULL);

#define VideoSetDisplayBrightness(a,b,c) \
	DeviceIoControl((HANDLE) a, IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS,  \
			(LPVOID)b, sizeof(DISPLAY_BRIGHTNESS), NULL,0,(LPDWORD) c, NULL);

HANDLE hLCD = 0;
BYTE* LightLevels = NULL;
DISPLAY_BRIGHTNESS curLightLevel;

bool initBacklight()
{
	hLCD = CreateFile("\\\\.\\LCD", GENERIC_READ,
					FILE_SHARE_READ | FILE_SHARE_WRITE,
					NULL, OPEN_EXISTING, 0, NULL);

	if (hLCD == INVALID_HANDLE_VALUE)
		return 0;

	BYTE Supported[256];
    DWORD nBytesReturned, nRes;

	nRes = VideoQuerySupportedBrightness(hLCD, Supported, &nBytesReturned);

    if (nRes == 0)
    {
    	CloseHandle(hLCD);
        return 0;
    }

	LightLevels = new BYTE[nBytesReturned+1];
	LightLevels[0] = nBytesReturned;
	LightLevels++;
	memcpy(LightLevels, Supported, nBytesReturned);

	nRes = VideoQueryDisplayBrightness(hLCD, &curLightLevel, &nBytesReturned);

    if (nRes == 0)
    {
        CloseHandle(hLCD);
        return 0;
    }

	return 1;
}

inline void updateBacklight()
{
	DWORD nBytesReturned;
	VideoSetDisplayBrightness(hLCD, &curLightLevel, &nBytesReturned);
}

inline void closeBacklight()
{
	CloseHandle(hLCD);
}

// Brightness
HDC hdc;
struct RAMP
{
    WORD Red[256];
    WORD Green[256];
    WORD Blue[256];
} ramp;

void updateBright()
{
	static float values[18] = {0, 1./3., 1./2.75, 1./2.5, 1./2.25, 1./2., 1./1.75,
					1./1.5, 1./1.25, 1. , 1.25, 1.5, 1.75, 2., 2.25, 2.5, 2.75, 3.};

	if (!appSettings.gamma)
		appSettings.gamma++;
	else if (appSettings.gamma > 17)
		appSettings.gamma = 17;

	for (int i = 0; i < 256; i++)
		ramp.Red[i] = ramp.Green[i] = ramp.Blue[i] = pow(i / 255.0, values[appSettings.gamma]) * 65535.0;

	SetDeviceGammaRamp(hdc, &ramp);
}


// Start With Windows functions
const TCHAR key[] = "Software\\Microsoft\\Windows\\CurrentVersion\\run";
void setStartWithWindows()
{
	TCHAR szFileName[MAX_PATH];
	DWORD size = GetModuleFileName(NULL, szFileName, MAX_PATH);

	HKEY hKey;
    if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hKey))
		RegSetValueEx(hKey, szAppName, 0, REG_SZ, (BYTE*)szFileName, size);
    RegCloseKey(hKey);
}

void unsetStartWithWindows()
{
	HKEY hKey;
    if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hKey))
		RegDeleteValue(hKey, szAppName);
    RegCloseKey(hKey);
}


// Hotkey
void toggleEnable(HWND hwnd)
{
	if (appSettings.enable)
	{
		DBG("UnregisterHotKeys");
		CheckMenuItem(tray_menu, ID_ENABLE, MF_UNCHECKED);
		for (int i = 0; i < HOTKEY_COUNT; i++)
			if (appSettings.hotkeys[i].enable)
				UnregisterHotKey(hwnd, i + HOTKEY_BASE);
	}
	else
	{
		DBG("RegisterHotKeys");
		CheckMenuItem(tray_menu, ID_ENABLE, MF_CHECKED);
		for (int i = 0; i < HOTKEY_COUNT; i++)
			if (appSettings.hotkeys[i].enable)
				RegisterHotKey(hwnd, i + HOTKEY_BASE,
							appSettings.hotkeys[i].mod,
							appSettings.hotkeys[i].key);
	}
	appSettings.enable = !appSettings.enable;
}


BOOL CALLBACK settingsWProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static HWND hHotkeyIn = NULL;
	static HWND hComboBox = NULL;
	static HWND hEnable = NULL;

    switch (msg)
    {
	case WM_COMMAND:
		DBG("WM_COMMAND");
        switch (LOWORD(wParam))
        {
		case IDC_COMBOBOX:
			if (HIWORD(wParam) == CBN_SELCHANGE)
			{
				int index = SendMessage(hComboBox, CB_GETCURSEL, 0, 0);
				SendMessage(hHotkeyIn, HKM_SETHOTKEY, MAKEWORD(appSettings.hotkeys[index].key, appSettings.hotkeys[index].mod), 0);
				SendMessage(hEnable, BM_SETCHECK, appSettings.hotkeys[index].enable ? BST_CHECKED : BST_UNCHECKED, 0);
			}
        }
        return 0;

    case WM_INITDIALOG:
		DBG("WM_INITDIALOG");

		hHotkeyIn = GetDlgItem(hwnd, IDC_HOTKEY);
		hComboBox = GetDlgItem(hwnd, IDC_COMBOBOX);
		hEnable = GetDlgItem(hwnd, IDC_ENABLE);

		for(int i = 0; i < HOTKEY_COUNT; i++)
			SendDlgItemMessage(hwnd, IDC_COMBOBOX, CB_ADDSTRING, (WPARAM)0, (LPARAM)strActions[i]);

		SendDlgItemMessage(hwnd, IDC_COMBOBOX, CB_SETCURSEL, 0, 0);
		if(appSettings.hotkeys[0].enable)
			CheckDlgButton(hwnd, IDC_ENABLE, BST_CHECKED);
		SendMessage(hHotkeyIn, HKM_SETHOTKEY, MAKEWORD(appSettings.hotkeys[0].key, appSettings.hotkeys[0].mod), 0);

		return TRUE;
		//SendMessage(hHotkeyIn, HKM_GETHOTKEY, 0 ,0);

    case WM_CLOSE:
		DBG("WM_CLOSE")
        EndDialog(hwnd, wParam);
		return TRUE;
    }
    return FALSE;
}

LRESULT CALLBACK mainWProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_HOTKEY:
        switch((int)wParam)
        {
        case IDH_TOGGLE:
            toggleEnable(hwnd);
            return 0;

        case IDH_POWEROFF:
            SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2);
            return 0;

        case IDH_BRIGHT_INC:
			DBG("BRIGHT_INC");
            appSettings.gamma--;
            updateBright();
            break;

        case IDH_BRIGHT_DEC:
            appSettings.gamma++;
            updateBright();
            break;

        case IDH_BRIGHT_DEF:
            appSettings.gamma = 9;
            updateBright();
            break;

        case IDH_LIGHT_INC:
            break;

        case IDH_LIGHT_DEC:
            break;

        case IDH_LIGHT_DEF:
            break;

        case IDH_CLOSE:
            DestroyWindow(hwnd);
            return 0;
        }

        break;

    case WM_USER:
        if (LOWORD(lParam) == WM_LBUTTONUP || LOWORD(lParam) == WM_RBUTTONUP ||	LOWORD(lParam) == WM_MBUTTONUP)
        {
            POINT p;
            GetCursorPos(&p);
            SetForegroundWindow(hwnd);
            TrackPopupMenu(tray_menu, 0, p.x, p.y, 0, hwnd, NULL);
        }
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case ID_ABOUT:
            MessageBox(hwnd,
                       "BrightCtrl 1.0 beta\n\n"
                       "Copyright (C) 2012, David Gonzalez\n\n"
                       "Freeware\n"
                       "http://DavidXL.es/\n",
                       "About", MB_OK);
            return 0;

        case ID_ENABLE:
			toggleEnable(hwnd);
            return 0;

        case ID_SETTINGS:
            DialogBox(hInst, MAKEINTRESOURCE(DLG_SETTINGS), NULL, (DLGPROC)settingsWProc);
            return 0;

        case ID_CLOSE:
            DestroyWindow(hwnd);
        }
        return 0;

    case WM_CREATE:
	{
		DBG("WM_CREATE");
		hdc = GetDC(NULL);

		HANDLE hFile = CreateFile("BrightCtrl.dat", GENERIC_READ, FILE_SHARE_READ,
							NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		/*
		if (hFile != INVALID_HANDLE_VALUE &&
			GetFileSize(hFile, NULL) == sizeof(appSettings))
		{
			ReadFile(hFile, &appSettings, sizeof(appSettings), NULL, NULL);
			CloseHandle(hFile);
		}
		*/

		tray_menu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1));
		tray_menu = GetSubMenu(tray_menu, 0); // InsertMenu();
		CheckMenuItem(tray_menu, ID_ENABLE, MF_CHECKED);
		tray_icon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON1));
		tray_data.cbSize = sizeof(tray_data);
		tray_data.hWnd = hwnd;
		tray_data.uID = IDC_TRAY;
		tray_data.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
		tray_data.uCallbackMessage = WM_USER;
		tray_data.hIcon = tray_icon;
		strncpy(tray_data.szTip, szAppName, sizeof(tray_data.szTip)-1);
		Shell_NotifyIcon(NIM_ADD, &tray_data);

		updateBright();

		if (appSettings.enable)
		{
			appSettings.enable = ! appSettings.enable;
			toggleEnable(hwnd);
		}
        return 0;
	}

    case WM_DESTROY:
	{
		// Save settings
		HANDLE hFile = CreateFile("BrightCtrl.dat", GENERIC_WRITE, 0, NULL,
							CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile != INVALID_HANDLE_VALUE)
		{
			WriteFile(hFile, &appSettings, sizeof(appSettings), NULL, NULL);
			CloseHandle(hFile);
		}

		// unload tray
        Shell_NotifyIcon(NIM_DELETE, &tray_data);
        DestroyIcon(tray_icon);
        DestroyMenu(tray_menu);

		// Unregister HotKeys
        if (appSettings.enable)
		{
			toggleEnable(hwnd);
		}

        PostQuitMessage(0);
        return 0;
	}
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd, int nCmdShow)
{
    if (FindWindow(szAppName,szAppName))
    {
    	DBG("The program is already open.");
    	return TRUE;
    }

	hInst = hInstance;
    WNDCLASS wc = {0, mainWProc, 0, 0, hInst, 0, 0, 0, 0, szAppName};
    if (!RegisterClass(&wc) || !CreateWindow(szAppName, szAppName,0,0,0,0,0,0,0,hInst,0))
		return FALSE;

	DBG("End of loading");
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


/** Others **/
/*
	char buf[129];
	wsprintf(buf,);
	MessageBox(0, "LCD open error", NULL, MB_ICONERROR);
*/
